今天我們將再次審視物件導向設計,並且帶入本書作者的新觀點。作者自己提到,接下來提到的看法可能並不會有太多的原創性,但是
稱之為新觀點,是指對於大多數開發人員而言它都可能是看待物件導向的新方式。
本章確實給了我一個新的角度去看物件導向,所以就來看看作者怎麼說吧!
首先我們先看看物件。
傳統對物件的看法就是具有方法的資料;具有描述問題領域中狀態的資料以及增加其處理資料的方法。
我們應該從概念視角出發(回顧三種軟體開發的視角),也就是物件是具有責任的一個實體(責任將決定行為);有時也會把物件視為具有特定行為的實體。
這樣定義的好處:使我們專注物件的意圖與行為,而無需太早關心細節以及如何實作。
同時,我們只關注物件的公開介面。我們無需知道物件內部如何運作、如何處理資訊,以及需要什麼樣其他的資訊。我們可以將責任全權委任給它。
將焦點放在動機,而非實作。
接著,我們來看封裝。
傳統上,我們會認為封裝就是將「資料隱藏」。
但實際上,我們可以把封裝視為任何形式的隱藏。除了隱藏資料外,也可以是隱藏:
實作細節的部分,就如同 Day7 提到 Shape
的例子。我們把實作的細節「封裝」了起來。
該例還有許多種上述的封裝類型,值得大家再回頭看看,我們這裡把他們點出來。
Point
、Line
、Square
以及 Circle
中的資料對其他所有物件是隱藏的Circle
中的 display
methodCircle
,圖中其他物件對 MyCircle
一無所知Shape
。早期物件導向的推廣者曾提倡過「類別的再利用」。這是什麼意思呢?
它指的是先建立基礎類別 (Base Class),然後再從這些基礎類別衍生出新類別。基礎類別常被稱為一般化類別、衍生的子類別被稱為特殊化類別。
舉例來說,今天我需要一個五角形,並對他做一些操作(各式各樣的繪製、塗色、消除等等),我們就如是定義了一個類別 Pentagon
。
而後來我們遇見了一個邊線較為特別的五角形,需要有一種新的繪製邊線的方式,那麼根據上面的描述,我們的做法會是從 Pentagon
衍生出一個類別 PentagonSpecialBorder
,其中繪製邊線的方法會有所不同。從 UML 圖,我們會如此表示。
如此的操作很直覺,但或許會產生一些問題:
此方式的問題 | 說明 |
---|---|
可能導致弱內聚 | 如果另外存在許多不同邊線類型,那麼 Pentagon 就必須另外關心所有不同的變現繪製。這樣使類別處理更多問題 |
減少再利用的可能性 | 不同繪製邊線的 methods 無法再利用,其他地方無法存取 |
無法根據變化良好伸縮 | 為了因應所有選擇,需要不斷地特殊化 Pentagon 類別,不幸地話(很常發˙生)有可能發生程式碼重複的問題 |
好的繼承方式,可能會是依照相同的行為分類。
GoF 再次現身開示:
考慮你的設計中哪些地方可能變化。這種方式與關注會導致重新設計的原因相反。它不是考慮什麼會迫使你的設計改變,而是考慮你怎樣才能夠不重新設計的狀況下進行改變。這裡的關鍵在於封裝發生變化的概念,這是許多設計模式的主題。
簡言之,作者的說法是:
發現變化並將其封裝。
將封裝看成透過抽象類別或者介面隱藏類別(類型封裝),GoF 說的話或許就不會那麼難以理解。
舉個例子,今天我們需要對動物的不同特徵作建模。需求如下:
對於腿的部分來說,典型的做法會是設一個資料元素存放腿的數量,也許也需要相關的 method 來取出這項資料元素(例如 getLeg
)。複雜的部分在於處理行為上(移動方式)。
我們假設有兩種可能的移動方式:飛行和行走。兩種不同的方法有兩種不同的實作的程式碼。既然存在兩種不同 methods,我們可能有以下兩種解決方案:
Animal
類別,一個表示能行走、一個能飛。但後來會發現兩種方式都不太理想。因為在今天的情境下,我們只關注「移動方式」,如果我們再將「食性(肉食性、草食性、雜食性)行為」放進去考量的話,那麼各種排列組合⋯⋯ Jesus Christ, it'll never end.
當一個類別處理愈來愈多不同的變化,程式碼的內聚性會因此降低。
那麼我們可以換個方式思考,將 Animal
類別包含具有一個合適移動行為的物件,如圖
這個解決方案會是不久之後會提到的設計模式之一,大家可以先自行參透一下。程式碼實作的部分,也會在往後的介紹到的時候,做更詳細的補充。
明天我會繼續把 Chapter 8 的部分 cover 完,會簡單地提到共變性與可變性分析,以及一點點的敏捷開發。 See you guys tomorrow!